/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;

namespace Xceed.Silverlight.ListBox
{
  internal class RemovingItemTransitionAnimation : TransitionAnimation
  {
    internal RemovingItemTransitionAnimation( ListBoxAnimationSettings animationSettings )
      : base( animationSettings.GetRemovingItemDuration() )
    {
      PowerEase opacityEasing = new PowerEase();
      opacityEasing.EasingMode = EasingMode.EaseOut;
      opacityEasing.Power = 6;

      PowerEase itemsRemovedEasing = new PowerEase();
      itemsRemovedEasing.EasingMode = EasingMode.EaseOut;
      itemsRemovedEasing.Power = 6;

      PowerEase otherItemsEasing = new PowerEase();
      otherItemsEasing.EasingMode = EasingMode.EaseInOut;
      otherItemsEasing.Power = 6;

      m_opacityEasing = opacityEasing;
      m_removedItemsMovementEasing = itemsRemovedEasing;
      m_otherItemsMovementEasing = otherItemsEasing;

    }

    internal double RemovePositionX
    {
      get;
      private set;
    }

    internal double RemovePositionY
    {
      get;
      private set;
    }

    internal double RemoveWidth
    {
      get;
      private set;
    }

    internal double RemoveHeight
    {
      get;
      private set;
    }

    internal Orientation Orientation
    {
      get;
      set;
    }

    internal override void AddContainer( ListBoxContainer container )
    {
      //Nothing to do
    }

    internal override LayoutChangeInfo CreateLayoutChangeInfo()
    {
      return new LayoutChangeInfo();
    }

    internal override void DetermineLayoutChangesCore( Dictionary<ListBoxContainer, LayoutChangeInfo> changedElements )
    {
      //For this transition animation, I need to assess the position ("pixels") where the remove is performed.

      //I also need to know total items travel to impact added containers correctly.

      double lowestRemovalX = double.MaxValue;
      double lowestRemovalY = double.MaxValue;

      double highestRemovalX = double.MinValue;
      double highestRemovalY = double.MinValue;

      IEnumerable<ListBoxContainer> removedElements = from kvp in changedElements
                                                     let layoutChangeInfo = kvp.Value
                                                     let removedContainer = kvp.Key
                                                     where layoutChangeInfo.ChangeType == ChangeType.ElementRemoved
                                                     select removedContainer;

      bool hadRemoval = false;

      foreach( ListBoxContainer container in removedElements )
      {
        Point containerPosition = container.ActualComputedPosition;

        double elementHeight = container.ActualElementHeight;
        double elementWidth = container.AbsoluteElementWidth;

        if( containerPosition.X < lowestRemovalX )
        {
          lowestRemovalX = containerPosition.X;
        }

        if( containerPosition.Y < lowestRemovalY )
        {
          lowestRemovalY = containerPosition.Y;
        }

        if( ( containerPosition.X + elementWidth ) > highestRemovalX )
        {
          highestRemovalX = containerPosition.X + elementWidth;
        }

        if( ( containerPosition.Y + elementHeight ) > highestRemovalY )
        {
          highestRemovalY = containerPosition.Y + elementHeight;
        }

        hadRemoval = true;
      }


      if( hadRemoval )
      {
        this.RemovePositionX = lowestRemovalX;
        this.RemovePositionY = lowestRemovalY;
        this.RemoveWidth = highestRemovalX - lowestRemovalX;
        this.RemoveHeight = highestRemovalY - lowestRemovalY;
      }
    }

    internal override bool PrepareContainerCore( ListBoxContainer container, LayoutChangeInfo changeInfo )
    {
      switch( changeInfo.ChangeType )
      {
        case ChangeType.ElementAdded:
          this.PrepareAddedElement( container, changeInfo );
          break;

        case ChangeType.ElementRemoved:
          this.PrepareRemovedElement( container, changeInfo );
          break;

        //If element is changed, don't change a thing.
        case ChangeType.ElementChanged:
          this.PrepareChangedElement( container, changeInfo );
          break;
        default:
          break;
      }

      //systematically return true. All elements flagged as changed are handled in this transition.
      return true;
    }

    internal override bool UpdateContainerCore( double normalizedElapsedTime, ListBoxContainer container )
    {

      if( container == null )
        throw new ListBoxInternalException();


      container.UpdateAnimations( normalizedElapsedTime );

      //There is no custom animations in this transition, only those handled by the EasingFunctions.
      //Since I have no custom timeline, I do not have to decide when its "finished". Returning false
      //will just make it so that the animation will proceed for every frame of every item.
      return false;
    }

    internal override void CleanContainerCore( ListBoxContainer container )
    {
      if( container == null )
        throw new ListBoxInternalException();

    }

    private void PrepareAddedElement( ListBoxContainer container, LayoutChangeInfo changeInfo )
    {

      if( changeInfo == null )
        throw new ListBoxInternalException();

      //In the case of an Remove action, added elements are those brought into the layout because some content is removed from the layout.
      //In that case, this class will animate them from the height of removal to their respective position

      SizeAnimation sizeAnimation = new SizeAnimation();
      OpacityAnimation opacityAnimation = new OpacityAnimation();
      PositionAnimation positionAnimation = new PositionAnimation();

      ( ( IAnimation )opacityAnimation ).AnimationDirection = AnimationDirection.ToReferencePosition;
      ( ( IAnimation )opacityAnimation ).EasingFunction = m_opacityEasing;
      ( ( IAnimation )opacityAnimation ).ParentTransition = this;

      ( ( IAnimation )sizeAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )sizeAnimation ).ParentTransition = this;

      ( ( IAnimation )positionAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )positionAnimation ).ParentTransition = this;

      if( this.Orientation == Orientation.Vertical )
      {
        positionAnimation.XChange = double.NaN;
        positionAnimation.YChange = this.RemoveHeight;

        sizeAnimation.MaximumHeightChange = -this.RemoveHeight;
      }
      else
      {
        positionAnimation.XChange = this.RemoveWidth;
        positionAnimation.YChange = double.NaN;

        sizeAnimation.MaximumWidthChange = -this.RemoveWidth;
      }

      sizeAnimation.WidthChange = double.NaN;
      sizeAnimation.HeightChange = double.NaN;

      opacityAnimation.OpacityChange = -changeInfo.OpacityChange;

      container.AddAnimation( sizeAnimation );
      container.AddAnimation( opacityAnimation );
      container.AddAnimation( positionAnimation );

    }

    private void PrepareChangedElement( ListBoxContainer container, LayoutChangeInfo changeInfo )
    {

      if( changeInfo == null )
        throw new ListBoxInternalException();

      //In the case of an Remove action, added elements are those brought into the layout because some content is removed from the layout.
      //In that case, this class will animate them from the height of removal to their respective position
      PositionAnimation positionAnimation = new PositionAnimation();
      SizeAnimation sizeAnimation = new SizeAnimation();
      RotateAnimation rotationAnimation = new RotateAnimation();
      ScaleAnimation scaleAnimation = new ScaleAnimation();

      ( ( IAnimation )positionAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )positionAnimation ).ParentTransition = this;
      ( ( IAnimation )sizeAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )sizeAnimation ).ParentTransition = this;
      ( ( IAnimation )rotationAnimation ).ParentTransition = this;
      ( ( IAnimation )rotationAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )scaleAnimation ).ParentTransition = this;
      ( ( IAnimation )scaleAnimation ).EasingFunction = m_otherItemsMovementEasing;

      positionAnimation.XChange = changeInfo.XChange;
      positionAnimation.YChange = changeInfo.YChange;
      rotationAnimation.AngleChange = changeInfo.AngleChange;
      if( this.Orientation == Orientation.Vertical )
      {
        sizeAnimation.MaximumHeightChange = -this.RemoveHeight;
      }
      else
      {
        sizeAnimation.MaximumWidthChange = -this.RemoveWidth;
      }

      sizeAnimation.WidthChange = double.NaN;
      sizeAnimation.HeightChange = double.NaN;
      scaleAnimation.ScaleChange = changeInfo.ScaleChange;

      container.AddAnimation( rotationAnimation );
      container.AddAnimation( positionAnimation );
      container.AddAnimation( sizeAnimation );
      container.AddAnimation( scaleAnimation );
    }

    private void PrepareRemovedElement( ListBoxContainer container, LayoutChangeInfo changeInfo )
    {
      //Removed items in the case of a Remove are items that are shifted out of view ( they are removed )

      if( changeInfo == null )
        throw new ListBoxInternalException();

      //Animation for these items is to push them away by the amount of inserted items.
      Point elementActualPosition = container.ActualComputedPosition;
      SizeAnimation sizeAnimation = new SizeAnimation();
      OpacityAnimation opacityAnimation = new OpacityAnimation();
      RotateAnimation rotateAnimation = new RotateAnimation();

      ( ( IAnimation )sizeAnimation ).EasingFunction = m_removedItemsMovementEasing;
      ( ( IAnimation )sizeAnimation ).ParentTransition = this;
      ( ( IAnimation )opacityAnimation ).EasingFunction = m_opacityEasing;
      ( ( IAnimation )opacityAnimation ).ParentTransition = this;
      ( ( IAnimation )opacityAnimation ).AnimationDirection = AnimationDirection.FromReferencePosition;
      ( ( IAnimation )rotateAnimation ).EasingFunction = m_removedItemsMovementEasing;
      ( ( IAnimation )rotateAnimation ).ParentTransition = this;
      rotateAnimation.AngleChange = changeInfo.AngleChange;

      sizeAnimation.WidthChange = double.NaN; // do not change its width
      sizeAnimation.HeightChange = double.NaN; // do not change its height.

      opacityAnimation.OpacityChange = changeInfo.OpacityChange;

      container.AddAnimation( rotateAnimation );
      container.AddAnimation( sizeAnimation );
      container.AddAnimation( opacityAnimation );

    }

    private IEasingFunction m_opacityEasing;
    private IEasingFunction m_removedItemsMovementEasing;
    private IEasingFunction m_otherItemsMovementEasing;
  }
}
